Popular Searches
Popular Course Categories
Popular Courses

Building Custom Modals, Dialogs, and Bottom Sheets

Building Custom Modals, Dialogs, and Bottom Sheets

UI Libraries, Themes, and Advanced Interface Design

Building Custom Modals, Dialogs, and Bottom Sheets in React Native

Course: React Native Training

Topic: Building Custom Modals, Dialogs, and Bottom Sheets

Course Link: React Native Training at JustAcademy

Demo Registration: Register for a Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to Modals, Dialogs, and Bottom Sheets

Modals, dialogs, and bottom sheets are interactive interface elements used to display information, collect user input, request confirmation, and provide additional actions without navigating away from the current screen.

In React Native applications, these elements are commonly used in login forms, confirmation messages, shopping carts, filters, profile editing, payment flows, and settings screens. They help organize the interface by displaying additional content only when it is needed.

Although these elements may look similar, they have different purposes and interaction patterns.

1.1 What Is a Modal?

A modal is a layer that appears above the current screen and temporarily directs the user's attention to a specific task. It may cover the entire screen or display a smaller panel over a dimmed background.

Examples include:

  • Opening a login or registration form.
  • Displaying product details.
  • Showing a full-screen image preview.
  • Presenting a multi-step form.
  • Displaying an important notice.

1.2 What Is a Dialog?

A dialog is a focused message or interaction panel that asks the user to read information, make a decision, or confirm an action.

Examples include:

  • Confirming whether to delete an account.
  • Asking whether unsaved changes should be discarded.
  • Confirming an order cancellation.
  • Displaying an error message with an action to retry.

1.3 What Is a Bottom Sheet?

A bottom sheet is a panel that slides upward from the bottom edge of the screen. It may occupy a small portion of the screen or expand to display more content.

Examples include:

  • Displaying sorting and filtering options.
  • Showing sharing actions.
  • Presenting product variations.
  • Displaying location or map details.
  • Showing a list of actions for a selected item.

2. Learning Objectives

After completing this topic, learners should be able to:

  • Understand the differences between modals, dialogs, and bottom sheets.
  • Use React Native's built-in Modal component.
  • Create custom overlays with reusable React Native components.
  • Implement confirmation dialogs and handle user responses.
  • Build bottom sheets with interactive content.
  • Control visibility using React state.
  • Add animations, backdrops, and dismissal behavior.
  • Manage keyboard interaction, safe areas, and accessibility.
  • Use a third-party bottom sheet library when advanced gestures are required.
  • Test overlays on Android and iOS.

3. Modal vs. Dialog vs. Bottom Sheet

Feature Modal Dialog Bottom Sheet
Purpose Displays additional content above the current screen Focuses attention on a message or decision Displays contextual content and actions from the bottom
Typical position Centered, full-screen, or another configured presentation Usually centered or positioned as a compact panel Attached to the bottom edge
Content size Small to full-screen Usually compact Partial-height or expandable
Interaction Forms, details, workflows, and custom content Confirmation and short decisions Filtering, actions, selections, and contextual details
Dismissal Close button, action, or supported gestures Action buttons or supported dismissal Close button, backdrop, or swipe gesture when enabled
Example Login form Delete confirmation Product filter panel

A dialog can technically be implemented inside a modal, and a bottom sheet can also be presented as a modal. These terms describe different aspects of an interface rather than completely separate technologies.

4. Understanding React Native's Modal Component

React Native provides a built-in Modal component for presenting content above the enclosing view. Its visibility is controlled with the visible property.

Official documentation: React Native Modal.

4.1 Important Modal Properties

Property Purpose
visible Controls whether the modal is displayed.
animationType Controls the presentation animation: none, slide, or fade.
transparent Allows the underlying screen to show through the modal window.
onRequestClose Handles Android hardware-back dismissal and relevant platform close requests.
onShow Runs when the modal has been shown.
onDismiss Runs after dismissal on supported platforms.
presentationStyle Controls supported iOS modal presentation styles.
statusBarTranslucent Controls whether the Android modal can extend beneath the status bar.

5. Building a Basic Custom Modal

The first step is to create a modal whose visibility can be controlled by a button. The modal can contain a title, description, and close action.

5.1 Complete Example

import React, { useState } from 'react';
import {
  View,
  Text,
  Modal,
  Pressable,
  StyleSheet
} from 'react-native';

export default function App() {
  const [modalVisible, setModalVisible] = useState(false);

  return (
    <View style={styles.screen}>
      <Text style={styles.heading}>Custom Modal Example</Text>

      <Pressable
        style={styles.openButton}
        onPress={() => setModalVisible(true)}
        accessibilityRole="button"
      >
        <Text style={styles.buttonText}>Open Modal</Text>
      </Pressable>

      <Modal
        visible={modalVisible}
        transparent
        animationType="fade"
        onRequestClose={() => setModalVisible(false)}
      >
        <View style={styles.overlay}>
          <View style={styles.modalCard}>
            <Text style={styles.title}>Welcome</Text>

            <Text style={styles.message}>
              This is a custom modal in React Native.
            </Text>

            <Pressable
              style={styles.closeButton}
              onPress={() => setModalVisible(false)}
              accessibilityRole="button"
              accessibilityLabel="Close modal"
            >
              <Text style={styles.buttonText}>Close</Text>
            </Pressable>
          </View>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#F3F4F6'
  },
  heading: {
    fontSize: 23,
    fontWeight: 'bold',
    marginBottom: 20
  },
  openButton: {
    backgroundColor: '#2563EB',
    paddingVertical: 12,
    paddingHorizontal: 20,
    borderRadius: 8
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: '600'
  },
  overlay: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: 'rgba(0, 0, 0, 0.5)'
  },
  modalCard: {
    width: '100%',
    maxWidth: 400,
    backgroundColor: '#FFFFFF',
    padding: 24,
    borderRadius: 16,
    alignItems: 'center'
  },
  title: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 12,
    color: '#111827'
  },
  message: {
    fontSize: 16,
    lineHeight: 24,
    textAlign: 'center',
    color: '#4B5563',
    marginBottom: 20
  },
  closeButton: {
    backgroundColor: '#2563EB',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8
  }
});

5.2 How the Code Works

  1. useState(false) initializes the modal as hidden.
  2. The Open Modal button updates the state to true.
  3. The visible property displays the modal when the state is true.
  4. transparent allows a custom overlay and modal card to be rendered.
  5. The overlay darkens the background behind the modal.
  6. The Close button sets visibility to false.
  7. onRequestClose handles the Android hardware-back request.
  8. animationType="fade" applies a fade presentation animation.

6. Customizing the Modal Appearance

A custom modal should match the application's design system. Developers can control its width, background, border radius, spacing, typography, shadows, and action buttons.

6.1 Important Design Properties

  • Width: Use a responsive width and a reasonable maximum width.
  • Padding: Provide enough space around the content.
  • Border radius: Use consistent rounded corners.
  • Backdrop: Use a suitable transparent overlay to separate the modal from the screen.
  • Typography: Use a clear title and readable body text.
  • Actions: Make primary and secondary actions visually distinct.
  • Elevation: Use appropriate shadow or separation where supported.

6.2 Making the Modal Responsive

A modal may look good on one phone but exceed the available height on another device. For longer content, use a scrollable container and respect safe areas.

import {
  Modal,
  View,
  ScrollView,
  Text
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

export function DetailsModal({ visible, onClose }) {
  return (
    <Modal
      visible={visible}
      animationType="slide"
      onRequestClose={onClose}
    >
      <SafeAreaView style={{ flex: 1 }}>
        <ScrollView contentContainerStyle={{ padding: 20 }}>
          <Text style={{ fontSize: 24, fontWeight: 'bold' }}>
            Details
          </Text>
          <Text style={{ marginTop: 12, lineHeight: 24 }}>
            Place longer modal content here. The content can
            scroll when it exceeds the available screen height.
          </Text>
        </ScrollView>
      </SafeAreaView>
    </Modal>
  );
}

This example uses SafeAreaView from react-native-safe-area-context. Install and configure that package according to its documentation if it is not already present in the project.

7. Building a Custom Confirmation Dialog

Confirmation dialogs are useful when an action may remove information, cancel an operation, or cause a significant change. The dialog should clearly explain the action and provide an explicit way to confirm or cancel.

7.1 Example: Delete Confirmation

import React, { useState } from 'react';
import {
  View,
  Text,
  Modal,
  Pressable,
  StyleSheet
} from 'react-native';

export default function DeleteExample() {
  const [dialogVisible, setDialogVisible] = useState(false);
  const [status, setStatus] = useState('');

  const confirmDelete = () => {
    setDialogVisible(false);
    setStatus('Delete action confirmed.');
  };

  const cancelDelete = () => {
    setDialogVisible(false);
    setStatus('Delete action cancelled.');
  };

  return (
    <View style={styles.screen}>
      <Pressable
        style={styles.deleteButton}
        onPress={() => setDialogVisible(true)}
      >
        <Text style={styles.buttonText}>Delete Item</Text>
      </Pressable>

      {status ? <Text style={styles.status}>{status}</Text> : null}

      <Modal
        visible={dialogVisible}
        transparent
        animationType="fade"
        onRequestClose={cancelDelete}
      >
        <View style={styles.overlay}>
          <View
            style={styles.dialog}
            accessibilityRole="alert"
          >
            <Text style={styles.title}>Delete Item?</Text>

            <Text style={styles.message}>
              This action may permanently remove the selected item.
              Do you want to continue?
            </Text>

            <View style={styles.actions}>
              <Pressable
                style={styles.cancelButton}
                onPress={cancelDelete}
                accessibilityRole="button"
              >
                <Text>Cancel</Text>
              </Pressable>

              <Pressable
                style={styles.confirmButton}
                onPress={confirmDelete}
                accessibilityRole="button"
              >
                <Text style={styles.buttonText}>Delete</Text>
              </Pressable>
            </View>
          </View>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20
  },
  deleteButton: {
    backgroundColor: '#B91C1C',
    padding: 14,
    borderRadius: 8
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: '600'
  },
  overlay: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: 'rgba(0, 0, 0, 0.5)'
  },
  dialog: {
    width: '100%',
    maxWidth: 400,
    padding: 22,
    backgroundColor: '#FFFFFF',
    borderRadius: 14
  },
  title: {
    fontSize: 21,
    fontWeight: 'bold',
    color: '#111827'
  },
  message: {
    marginTop: 12,
    lineHeight: 22,
    color: '#4B5563'
  },
  actions: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    marginTop: 24,
    gap: 12
  },
  cancelButton: {
    padding: 12,
    borderRadius: 8,
    backgroundColor: '#E5E7EB'
  },
  confirmButton: {
    padding: 12,
    borderRadius: 8,
    backgroundColor: '#B91C1C'
  },
  status: {
    marginTop: 16
  }
});

7.2 Important Implementation Details

  • Cancel closes the dialog without confirming the action.
  • Delete represents the confirmation path; in a real application, connect it to the actual deletion operation.
  • The message clearly explains the consequence of the action.
  • The destructive action uses a different visual style from Cancel.
  • The Android back request closes the dialog through the same cancellation handler.

8. Creating a Reusable Dialog Component

Instead of rebuilding a confirmation dialog on every screen, create a reusable component with configurable properties. This reduces duplication and keeps dialog behavior consistent.

8.1 Reusable Dialog Example

import React from 'react';
import {
  Modal,
  View,
  Text,
  Pressable,
  StyleSheet
} from 'react-native';

type ConfirmDialogProps = {
  visible: boolean;
  title: string;
  message: string;
  confirmLabel?: string;
  cancelLabel?: string;
  onConfirm: () => void;
  onCancel: () => void;
};

export default function ConfirmDialog({
  visible,
  title,
  message,
  confirmLabel = 'Confirm',
  cancelLabel = 'Cancel',
  onConfirm,
  onCancel
}: ConfirmDialogProps) {
  return (
    <Modal
      visible={visible}
      transparent
      animationType="fade"
      onRequestClose={onCancel}
    >
      <View style={styles.overlay}>
        <View style={styles.dialog}>
          <Text style={styles.title}>{title}</Text>
          <Text style={styles.message}>{message}</Text>

          <View style={styles.actions}>
            <Pressable
              onPress={onCancel}
              accessibilityRole="button"
              style={styles.cancel}
            >
              <Text>{cancelLabel}</Text>
            </Pressable>

            <Pressable
              onPress={onConfirm}
              accessibilityRole="button"
              style={styles.confirm}
            >
              <Text style={{ color: '#FFFFFF' }}>
                {confirmLabel}
              </Text>
            </Pressable>
          </View>
        </View>
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  overlay: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: 'rgba(0,0,0,0.5)'
  },
  dialog: {
    width: '100%',
    maxWidth: 400,
    backgroundColor: '#FFFFFF',
    padding: 22,
    borderRadius: 14
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold'
  },
  message: {
    marginTop: 10,
    lineHeight: 22,
    color: '#4B5563'
  },
  actions: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    gap: 12,
    marginTop: 22
  },
  cancel: {
    padding: 12,
    backgroundColor: '#E5E7EB',
    borderRadius: 8
  },
  confirm: {
    padding: 12,
    backgroundColor: '#2563EB',
    borderRadius: 8
  }
});

8.2 Using the Reusable Dialog

<ConfirmDialog
  visible={showDialog}
  title="Discard Changes?"
  message="Your unsaved changes will be lost."
  confirmLabel="Discard"
  cancelLabel="Keep Editing"
  onConfirm={discardChanges}
  onCancel={() => setShowDialog(false)}
/>

In a production implementation, ensure that the component is placed inside the appropriate screen or application tree and that the state and callback functions are defined by its parent.

9. Handling Backdrop Presses

A backdrop is the dimmed area behind a modal or dialog. Some interfaces allow users to tap the backdrop to dismiss the overlay. This behavior is useful for non-destructive panels, but it may be inappropriate for forms or important confirmations where accidental dismissal could cause confusion.

9.1 Implementing a Dismissible Backdrop

One way to implement a custom overlay is to use a full-screen Pressable as the backdrop and place the dialog above it. Ensure that pressing inside the dialog does not accidentally trigger the backdrop's close action.

<View style={styles.overlay}>
  <Pressable
    style={StyleSheet.absoluteFill}
    onPress={onClose}
    accessibilityRole="button"
    accessibilityLabel="Dismiss dialog"
  />

  <View style={styles.dialog}>
    <Text>Dialog content</Text>
  </View>
</View>

For production code, test touch handling carefully and consider whether a backdrop dismissal should be available at all. A visible close button should be provided when appropriate.

10. Building a Bottom Sheet from Scratch

A simple bottom sheet can be built using React Native's Modal, a transparent backdrop, and a panel aligned to the bottom of the screen. This approach is suitable for basic interfaces, although it does not automatically provide advanced dragging, snapping, or gesture coordination.

10.1 Basic Bottom Sheet Example

import React, { useState } from 'react';
import {
  View,
  Text,
  Modal,
  Pressable,
  StyleSheet
} from 'react-native';

export default function BottomSheetExample() {
  const [visible, setVisible] = useState(false);

  return (
    <View style={styles.screen}>
      <Pressable
        style={styles.openButton}
        onPress={() => setVisible(true)}
        accessibilityRole="button"
      >
        <Text style={styles.buttonText}>Open Bottom Sheet</Text>
      </Pressable>

      <Modal
        visible={visible}
        transparent
        animationType="slide"
        onRequestClose={() => setVisible(false)}
      >
        <View style={styles.modalRoot}>
          <Pressable
            style={styles.backdrop}
            onPress={() => setVisible(false)}
            accessibilityRole="button"
            accessibilityLabel="Close bottom sheet"
          />

          <View style={styles.sheet}>
            <View style={styles.handle} />

            <Text style={styles.title}>
              Choose an Action
            </Text>

            <Pressable style={styles.option} onPress={() => setVisible(false)}>
              <Text>View Profile</Text>
            </Pressable>

            <Pressable style={styles.option} onPress={() => setVisible(false)}>
              <Text>Share Item</Text>
            </Pressable>

            <Pressable style={styles.option} onPress={() => setVisible(false)}>
              <Text>Report Item</Text>
            </Pressable>

            <Pressable
              style={styles.cancelButton}
              onPress={() => setVisible(false)}
            >
              <Text style={styles.buttonText}>Close</Text>
            </Pressable>
          </View>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  openButton: {
    backgroundColor: '#2563EB',
    padding: 14,
    borderRadius: 8
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: '600'
  },
  modalRoot: {
    flex: 1,
    justifyContent: 'flex-end'
  },
  backdrop: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0,0,0,0.45)'
  },
  sheet: {
    backgroundColor: '#FFFFFF',
    padding: 24,
    paddingBottom: 32,
    borderTopLeftRadius: 22,
    borderTopRightRadius: 22
  },
  handle: {
    width: 44,
    height: 5,
    borderRadius: 4,
    backgroundColor: '#D1D5DB',
    alignSelf: 'center',
    marginBottom: 20
  },
  title: {
    fontSize: 21,
    fontWeight: 'bold',
    marginBottom: 16
  },
  option: {
    paddingVertical: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#E5E7EB'
  },
  cancelButton: {
    marginTop: 18,
    alignItems: 'center',
    backgroundColor: '#2563EB',
    padding: 13,
    borderRadius: 8
  }
});

10.2 Understanding the Bottom Sheet

  • modalRoot fills the modal window and aligns the sheet to the bottom.
  • backdrop dims the rest of the screen.
  • sheet contains the bottom sheet's content.
  • handle visually suggests that the panel can be dragged.
  • animationType="slide" animates the modal window.
  • Option buttons perform their actions and close the sheet in this example.

Important: The handle in this example is visual only. This implementation does not support dragging the sheet by the handle. Use a gesture-enabled bottom sheet library if users need to expand, collapse, or dismiss the sheet by swiping.

11. Using a Bottom Sheet Library

For more advanced bottom sheets, a dedicated library can provide snapping points, swipe gestures, backdrop behavior, scrollable content, keyboard handling, and smoother interactions.

One popular option is React Native Bottom Sheet. Its modal API provides methods such as present() and dismiss(), and the library supports configurable sheet behavior. Read the Bottom Sheet Modal documentation.

11.1 Installation

Install the package and its required dependencies according to the official instructions for your React Native or Expo setup.

npm install @gorhom/bottom-sheet

For Expo projects, the documented Expo installation flow commonly uses:

npx expo install react-native-reanimated react-native-gesture-handler

Check the current library documentation for version compatibility, Reanimated configuration, and the required root view setup. The exact installation steps depend on the project's framework and versions.

11.2 Example with BottomSheetModal

import React, { useCallback, useRef } from 'react';
import {
  View,
  Text,
  Button,
  StyleSheet
} from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import {
  BottomSheetModal,
  BottomSheetModalProvider,
  BottomSheetView
} from '@gorhom/bottom-sheet';

export default function App() {
  const bottomSheetRef = useRef<BottomSheetModal>(null);

  const openSheet = useCallback(() => {
    bottomSheetRef.current?.present();
  }, []);

  const closeSheet = useCallback(() => {
    bottomSheetRef.current?.dismiss();
  }, []);

  return (
    <GestureHandlerRootView style={styles.container}>
      <BottomSheetModalProvider>
        <View style={styles.screen}>
          <Button
            title="Open Bottom Sheet"
            onPress={openSheet}
          />
        </View>

        <BottomSheetModal
          ref={bottomSheetRef}
          snapPoints={['40%', '75%']}
        >
          <BottomSheetView style={styles.content}>
            <Text style={styles.title}>
              Bottom Sheet Content
            </Text>

            <Text style={styles.description}>
              This sheet can expand between configured snap points.
            </Text>

            <Button title="Close" onPress={closeSheet} />
          </BottomSheetView>
        </BottomSheetModal>
      </BottomSheetModalProvider>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  screen: {
    flex: 1,
    justifyContent: 'center',
    padding: 24
  },
  content: {
    flex: 1,
    padding: 24
  },
  title: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 12
  },
  description: {
    marginBottom: 20,
    lineHeight: 22
  }
});

11.3 Understanding the Library Example

  • BottomSheetModalProvider supplies the modal context required by the library.
  • BottomSheetModal represents the bottom sheet modal.
  • useRef stores a reference to the modal instance.
  • present() opens the modal.
  • dismiss() dismisses the modal.
  • snapPoints defines the sheet's configured resting heights.
  • BottomSheetView holds the sheet's content.

Follow the library's current documentation for exact setup and configuration requirements. Do not mix APIs from different major versions without checking compatibility.

12. Adding Animations to Custom Modals

Animations help users understand when an overlay appears or disappears. React Native's built-in Modal supports the none, slide, and fade animation types.

12.1 Available Modal Animations

Animation Behavior Suitable Use
none Displays without a built-in entrance animation Simple or immediate presentations
fade Fades the modal into view Confirmation dialogs and compact overlays
slide Slides the modal into view Panels and full-screen flows

12.2 Creating Custom Animation

For more control over a modal card's opacity or movement, developers can use React Native's Animated API. A custom animation requires appropriate state and animation lifecycle handling. For interactive dragging and advanced gesture coordination, a gesture and animation library may be more suitable.

Reference: React Native Animated API.

13. Handling Keyboard Interaction

Modals often contain text fields, such as login inputs, search fields, or forms. When the keyboard opens, it may cover part of the modal. Use keyboard-aware layouts and test the behavior on both Android and iOS.

13.1 Using KeyboardAvoidingView

import React from 'react';
import {
  Modal,
  View,
  TextInput,
  Button,
  KeyboardAvoidingView,
  Platform
} from 'react-native';

export function LoginModal({ visible, onClose }) {
  return (
    <Modal
      visible={visible}
      animationType="slide"
      onRequestClose={onClose}
    >
      <KeyboardAvoidingView
        style={{ flex: 1, justifyContent: 'center', padding: 24 }}
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      >
        <View>
          <TextInput
            placeholder="Email address"
            keyboardType="email-address"
            autoCapitalize="none"
            style={{
              borderWidth: 1,
              borderColor: '#9CA3AF',
              padding: 12,
              marginBottom: 12,
              borderRadius: 8
            }}
          />

          <TextInput
            placeholder="Password"
            secureTextEntry
            style={{
              borderWidth: 1,
              borderColor: '#9CA3AF',
              padding: 12,
              marginBottom: 16,
              borderRadius: 8
            }}
          />

          <Button title="Close" onPress={onClose} />
        </View>
      </KeyboardAvoidingView>
    </Modal>
  );
}

13.2 Keyboard Best Practices

  • Ensure the focused field remains visible when the keyboard opens.
  • Use suitable keyboard types for email addresses, numbers, and other inputs.
  • Provide a clear action to submit or dismiss the keyboard.
  • Test long forms on smaller devices.
  • Check the behavior with platform-specific keyboard settings.

14. Accessibility Considerations

Overlays can disrupt navigation for users who rely on screen readers, keyboard navigation, or other assistive technologies. Accessibility should be part of the design and implementation process.

14.1 Accessibility Checklist

  • Provide a meaningful title and clear description.
  • Use appropriate accessibility roles for buttons and interactive controls.
  • Give icon-only controls accessible labels.
  • Ensure text and controls have sufficient contrast.
  • Provide an obvious close or cancel action where appropriate.
  • Make the Android back-button behavior predictable.
  • Ensure the dialog's content is accessible without requiring precise gestures.
  • Prevent background interaction when the overlay is intended to be modal.
  • Test screen-reader focus and announcements on both platforms.

15. Modal and Bottom Sheet Design Best Practices

  1. Use overlays purposefully: Avoid showing modals for information that could be displayed directly on the screen.
  2. Keep dialog messages concise: Clearly explain the decision or action required.
  3. Use meaningful actions: Prefer labels such as Save, Cancel, Delete, or Continue instead of vague labels.
  4. Prevent accidental data loss: Confirm destructive actions and warn about unsaved changes.
  5. Make content responsive: Support small screens, larger text, and different orientations where applicable.
  6. Manage dismissal: Decide whether tapping the backdrop or pressing the Android back button should close the overlay.
  7. Handle asynchronous actions: Prevent repeated submissions while saving or processing data.
  8. Use bottom sheets for contextual actions: They work well for filters, sharing, and item-specific options.
  9. Keep animations purposeful: Use animations that clarify transitions without delaying the user.
  10. Test on real devices: Check gestures, keyboard behavior, safe areas, and platform-specific differences.

16. Common Errors and Troubleshooting

Problem Possible Cause Solution
Modal does not appear The visibility state is false or the component is not mounted Check the state and verify the component is rendered in the expected tree.
Modal cannot be closed with Android back onRequestClose is missing or does not update visibility Implement onRequestClose and update the relevant state.
Backdrop does not cover the screen Incorrect flex layout or overlay positioning Use a full-screen container and check the overlay styles.
Backdrop tap closes the panel unexpectedly Touch events are reaching the backdrop from the panel Review the view hierarchy and touch handling.
Dialog is too wide Fixed width exceeds the available screen space Use a responsive width and a suitable maximum width.
Modal content is clipped Content is taller than the available window Use a scrollable container and account for safe areas.
Keyboard covers input fields Keyboard-aware layout is missing or unsuitable Test KeyboardAvoidingView and adjust the layout for the platform.
Bottom sheet cannot be dragged A basic modal panel has no gesture implementation Use a gesture-enabled bottom sheet library.
Bottom sheet gestures conflict with scrolling Gesture and scrollable configuration is incorrect Follow the library's guidance for integrated scrollables and gestures.
Content appears beneath system bars Safe areas or modal presentation settings are incorrect Check safe-area handling and platform-specific modal properties.
Multiple dialogs appear at once Several visibility states are active simultaneously Coordinate overlay state and define which overlays can coexist.

17. Mini Project: Product Details and Actions

Project objective: Create a shopping application screen where users can open product details in a modal, confirm removal of an item with a dialog, and open a bottom sheet for sorting or filtering.

17.1 Requirements

  • Display a product card with a name and price.
  • Provide a button to open product details.
  • Display a confirmation dialog before removing a product.
  • Provide a bottom sheet with sorting options.
  • Include close and cancel actions.
  • Use consistent styling for buttons, panels, and text.
  • Handle Android back-button behavior.
  • Ensure content remains accessible on smaller screens.

17.2 Suggested Folder Structure

ShoppingApp/
├── App.tsx
└── src/
    ├── components/
    │   ├── ProductCard.tsx
    │   ├── ProductDetailsModal.tsx
    │   ├── ConfirmDialog.tsx
    │   └── SortBottomSheet.tsx
    ├── screens/
    │   └── ProductScreen.tsx
    └── data/
        └── products.ts

17.3 Implementation Steps

  1. Create the product listing screen.
  2. Build a reusable product card.
  3. Implement a modal for displaying product details.
  4. Create a reusable confirmation dialog.
  5. Build a bottom sheet for sorting options.
  6. Manage visibility and selection state.
  7. Connect buttons to the relevant actions.
  8. Add backdrop and close behavior where appropriate.
  9. Test on Android and iOS.
  10. Verify accessibility, scrolling, and keyboard behavior.

18. Practice Exercises

  1. Create a modal that displays a welcome message.
  2. Build a confirmation dialog for deleting a task.
  3. Create a reusable dialog component with configurable title and actions.
  4. Implement a backdrop that dismisses a non-destructive modal.
  5. Build a bottom sheet containing three action options.
  6. Extend the bottom sheet using a gesture-enabled library.
  7. Create a login modal with email and password fields.
  8. Prevent duplicate form submissions while a request is in progress.
  9. Test Android hardware-back behavior.
  10. Improve accessibility by adding appropriate labels and roles.

19. Interview Questions and Answers

Q1. What is a modal in React Native?

Answer: A modal presents content above the current screen and temporarily focuses the user on a task or additional information.

Q2. What is the React Native Modal component?

Answer: It is a built-in component used to display content in a separate modal presentation above the enclosing view.

Q3. What is the difference between a modal and a dialog?

Answer: A modal is a presentation mechanism for additional content, while a dialog is generally a focused message or decision interface that may be implemented using a modal.

Q4. What is a bottom sheet?

Answer: A bottom sheet is a panel that appears from the bottom of the screen and presents contextual information or actions.

Q5. What does the visible property do?

Answer: It controls whether a React Native Modal is displayed.

Q6. What is the purpose of onRequestClose?

Answer: It handles close requests such as the Android hardware-back button for a modal.

Q7. How can a modal be animated?

Answer: The built-in Modal supports none, fade, and slide animation types. Custom animations can be implemented using suitable animation APIs.

Q8. How do you make a reusable dialog?

Answer: Create a component with properties for visibility, title, message, action labels, and callback functions. This allows multiple screens to reuse the same dialog.

Q9. How can a basic bottom sheet be implemented?

Answer: Use a transparent modal, a backdrop, and a panel aligned to the bottom of the screen. Add a dedicated gesture library if dragging and snapping are required.

Q10. Why use a bottom sheet library?

Answer: A library can provide gesture handling, snapping points, animations, scroll integration, keyboard support, and configurable dismissal behavior.

Q11. How should a modal handle keyboard input?

Answer: Use suitable keyboard-aware layouts, keep focused inputs visible, and test on different devices and operating systems.

Q12. What accessibility considerations apply to dialogs?

Answer: Provide meaningful labels, readable text, appropriate accessibility roles, predictable focus and dismissal behavior, and sufficient contrast.

20. Quick Revision Checklist

  • Understand the differences between modals, dialogs, and bottom sheets.
  • Use React Native's built-in Modal component.
  • Control modal visibility with state.
  • Understand visible, transparent, animationType, and onRequestClose.
  • Create reusable confirmation dialogs.
  • Manage backdrop presses carefully.
  • Understand that a basic bottom sheet does not automatically support dragging.
  • Use a dedicated library for advanced bottom sheet gestures.
  • Handle keyboard behavior and safe areas.
  • Test accessibility, dismissal, and platform-specific behavior.

21. Official Learning Resources

22. Conclusion

Custom modals, dialogs, and bottom sheets help React Native developers create focused and interactive mobile experiences. The built-in Modal component is suitable for many overlay requirements, while reusable dialog components make confirmations and alerts consistent across an application.

Basic bottom sheets can be built with a modal and a bottom-aligned panel. When advanced dragging, snapping, and scroll coordination are required, a dedicated library such as React Native Bottom Sheet can provide those features. Good implementations also account for responsive layouts, keyboard behavior, accessibility, platform-specific dismissal, and performance.

To continue learning, explore the React Native Training course at JustAcademy or register for a course demo.

whatsapp